﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Go.Tools;
using Go.Model;
using Go.inteligence;

namespace Go.inteligence
{
    public class MiniMaxNode
    {
        private List<MiniMaxNode> _childrens;
        private MiniMaxNode _father;
        private Position _positionToPlay;
        private Boolean _isMin;
        private int _value;

        public int Value
        {
            get { return _value; }
            set { _value = value; }
        }
        private int _depth;

        private Player _actualNodePlayer;
        private Player _computerPlayer;


        static public Position getBestMove(Player player)
        {
            return (new MiniMaxNode(player, MINIMAXCONSTANT.MINIMAXDEPTH).getBestMove() );
        }

        public MiniMaxNode(Player player, int depth)
        {
            _father = null;
            _childrens = new List<MiniMaxNode>();
            //El nodo root es del jugador oponente
            _actualNodePlayer = player.Opponent;
            _computerPlayer = player;
            _value = MINIMAXCONSTANT.WITHOUTVALUE;
            _isMin = false;
            _positionToPlay = null;
            _depth = depth;

            doMinimax(); 
        }

        public MiniMaxNode(MiniMaxNode father, Player player,Player computerPlayer, bool isMin, Position positionToPlay, int depth)
        {
            _father = father;
            _childrens = new List<MiniMaxNode>();
            _actualNodePlayer = player;
            _value = MINIMAXCONSTANT.WITHOUTVALUE;
            _isMin = isMin;
            _positionToPlay = positionToPlay;
            _depth = depth;
            _computerPlayer = computerPlayer;
            _actualNodePlayer.simulatePutStone(positionToPlay);
            doMinimax();
            _actualNodePlayer.removeStone(positionToPlay);
        }

        public Position getBestMove() {
            MiniMaxNode bestNode = null; 
            foreach (MiniMaxNode node in this._childrens) {
                if (bestNode == null || bestNode.Value < node.Value)
                    bestNode = node;
            }

            return bestNode._positionToPlay;
        }

        private void doMinimax()
        {
            RulesValidator gameRules = RulesValidator.instance();
            if (gameRules.isEndOfGame(_actualNodePlayer)) {
                if (gameRules.isTheWinner(_computerPlayer))
                    computerWin();
                else
                    oponentWin();
                propagateResult();
                return;
            }
            if (_depth == 0)
            {
                calculateValue();
                return;
            }
            if (this._computerPlayer.Groups.Count == 0 && this._computerPlayer.Opponent.Groups.Count == 0)
            {
                Coordinate coordenadas;
                coordenadas.x = 5;
                coordenadas.y = 5;
                _positionToPlay = gameRules.getPosition(coordenadas);
                _childrens.Add(this);
            }
            else
            {
                List<Position> positions = new List<Position>(getPostionMoves());
                List<Position> positions2 = new List<Position>(positions);
                foreach (Position aPosition in positions2.Distinct())
                {
                    if (aPosition.isEmpty())
                    {
                        MiniMaxNode newChildren = new MiniMaxNode(this, _actualNodePlayer.Opponent, _computerPlayer, !_isMin, aPosition, _depth - 1);
                        _childrens.Add(newChildren);
                        if (!shouldMakePruning(newChildren.Value))
                        {
                            //Si se debe hacer poda se deja de calcular los hijos
                            return;
                        }
                    }
                }

            }
            propagateResult();
        }

        private void calculateValue() {
            Group opponentWeekGruop = getMostWeekGroup(_actualNodePlayer.Groups);
            Group playerWeekGruop = getMostWeekGroup(_actualNodePlayer.Opponent.Groups);
            int defenseLevel = calculateValueToDefenseGroup(playerWeekGruop);
            int attackLevel = calculateValueToAttackGroup(opponentWeekGruop);
            this._value = defenseLevel - attackLevel;
        }

        private void computerWin() {
            this._value = MINIMAXCONSTANT.INFINIT;
        }

        private void oponentWin()
        {
            this._value = -MINIMAXCONSTANT.INFINIT;
        }

        private void propagateResult() {
        if (!isRootNode())
            this._father.addResult(this.Value);
        }

        private bool isRootNode()
        {
            return this._father == null;
        }

        private bool isChildrenOfRootNode()
        {
            return (!isRootNode() && this._father.isRootNode());
        }

        private void addResult(int newValue)
        {
            if (isRootNode() && this.Value <= newValue)
                this._value = newValue;
            else if (!isRootNode() && (this.Value==MINIMAXCONSTANT.WITHOUTVALUE || this.Value >= newValue) )
                this._value = newValue;
        }

       //Devuelve true si se debe relizar la poda osea, en la estructura superior del arbol no hay un valor ya calculado
       //que sea mayor al valor que estoy calculando en el nodo inferior
       private bool shouldMakePruning(int aNewChildrenValue) {        
            if (this.Value > aNewChildrenValue)
                return true;

            if (this._father == null)
                return false;

            return this._father.shouldMakePruning(aNewChildrenValue);     
        }

        private List<Position> getPostionMoves() {
            Group opponentWeekGruop = getMostWeekGroup(_actualNodePlayer.Groups);
            Group playerWeekGruop = getMostWeekGroup(_actualNodePlayer.Opponent.Groups);
            
            int attackLevel = 0;
            int defenseLevel = 0;
            //Si no todavia tengo piezas en el tablero analizo solo la defensa.
            if (opponentWeekGruop != null)
            {
                attackLevel = calculateValueToAttackGroup(opponentWeekGruop);
            }
            if (playerWeekGruop != null)
            {
                defenseLevel = calculateValueToDefenseGroup(playerWeekGruop);
            }
            
            // Se invirtio el operador.        // versión inicial  
            //if (defenseLevel > attackLevel)
            //    return getPostionMoves(playerWeekGruop);
            //else
            //    return getPostionMoves(opponentWeekGruop);
            
            
            //Inteligencia Mejorada

            if (attackLevel < defenseLevel)
                return getPostionMoves(opponentWeekGruop);
            else if ((attackLevel > defenseLevel) && (defenseLevel == 0))
                return getPostionMoves(opponentWeekGruop);
            else if ((opponentWeekGruop.LibertyPositions.Count() == 1) && (playerWeekGruop.LibertyPositions.Count() == 1))
                return getPostionMoves(opponentWeekGruop);
            else
                return getPostionMoves(playerWeekGruop);
            

        }

        private List<Position> getPostionMoves(Group aGroup)
        {
            aGroup.updateGroup();
            List < Position > resultado = new List<Position>(aGroup.LibertyPositions);
            
            foreach ( Position aPosition in aGroup.kJugadas(MINIMAXCONSTANT.NUMBEROFPLAYSAFUTUREANALIZED))
                resultado.Add(aPosition);
            
            return resultado.Distinct().ToList();
        }

        private Group getMostWeekGroup(List<Group> groups){
            int minValue = MINIMAXCONSTANT.INFINIT;
            Group weekGroup = null;
            foreach (Group aGroup in groups) {
                int value = calculateValueToDefenseGroup(aGroup);
                if (value < minValue)
                {
                    minValue = value;
                    weekGroup = aGroup;
                } else if ( minValue <= value && weekGroup==null){
                    weekGroup = aGroup;
                }
            }        
            return weekGroup;
        }

        private int calculateValueToDefenseGroup(Group aGroup) {
            int result = MINIMAXCONSTANT.DEFENSELIBERTYGROUP * aGroup.LibertyPositions.Count() +
            MINIMAXCONSTANT.DEFENSELIBERTYGROUPINKMPLAYS * aGroup.kJugadas(MINIMAXCONSTANT.NUMBEROFPLAYSAFUTUREANALIZED).Count * aGroup.LibertyPositions.Count();
            if (aGroup.isAliveGroup())
                result += MINIMAXCONSTANT.DEFENSEISALIVEGROUP;
            return result;
        }

        private int calculateValueToAttackGroup(Group aGroup)
        {
            int result = MINIMAXCONSTANT.ATTACKLIBERTYGROUP * aGroup.LibertyPositions.Count() +
            MINIMAXCONSTANT.ATTACKLIBERTYGROUPINKMPLAYS * aGroup.kJugadas(MINIMAXCONSTANT.NUMBEROFPLAYSAFUTUREANALIZED).Count * aGroup.LibertyPositions.Count();
            if (aGroup.isAliveGroup())
                result += MINIMAXCONSTANT.ATTACKISALIVEGROUP;
            return result;
        }


    }
}
